package craky.keeper;

import java.awt.Image;
import java.awt.SplashScreen;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;

import com.caucho.hessian.client.HessianProxyFactory;
import com.sun.awt.AWTUtilities;

import craky.componentc.JCDialog;
import craky.componentc.JCFrame;
import craky.componentc.JCMessageBox;
import craky.keeper.bean.Category;
import craky.keeper.bean.ResultSet;
import craky.keeper.bean.User;
import craky.keeper.dialog.ServerConfigDialog;
import craky.keeper.dialog.UserInfoDialog;
import craky.keeper.remote.RemoteInterface;
import craky.keeper.skin.Skin;
import craky.keeper.util.KeeperConst;
import craky.keeper.util.KeeperUtil;
import craky.security.Checker;
import craky.security.Digest;
import craky.util.Config;
import craky.util.UIUtil;
import craky.util.Util;

public class KeeperApp
{
    private Config config, loginConfig;

    private MainFrame mainFrame;

    private LoginFrame loginFrame;

    private ServerConfigDialog serverConfigDialog;

    private Map<String, Skin> skinMap;

    private Skin currentSkin, currentPreviewSkin;

    private Timer gcTimer;

    private List<String> loginHistory;

    private Map<String, Category> payCategoryMap, incomeCategoryMap;

    private User currentUser;

    private String bannerName;

    private RemoteInterface remote;

    private Boolean firstUse;

    private String currentIP;

    private boolean categoriesLoaded;

    public KeeperApp()
    {
        this.config = new Config(KeeperConst.CONFIG_PATH);
        this.loginConfig = new Config(KeeperConst.LOGIN_CONFIG_PATH);
        this.loginHistory = new ArrayList<String>(KeeperConst.MAX_HISTORY_USER_COUNT);
        this.payCategoryMap = new LinkedHashMap<String, Category>();
        this.incomeCategoryMap = new LinkedHashMap<String, Category>();
        initBannerName();
        connectServer(null);
    }

    private void connectServer(String oldIp)
    {
        String ip = config.getProperty(KeeperConst.SERVER_IP);

        if(oldIp != null || (ip == null || !Util.checkIPV4(ip)))
        {
            closeSplash();
            serverConfigDialog = new ServerConfigDialog(this, null, oldIp);
            serverConfigDialog.setVisible(true);

            if(firstUse == null)
            {
                System.exit(0);
            }
        }
        else
        {
            try
            {
                testConnection(ip);
                load();
            }
            catch(Exception e)
            {
                connectServer(ip);
            }
        }
    }

    public void testConnection(String ip) throws Exception
    {
        final String defaultPort = "30330";
        String port = config.getProperty(KeeperConst.SERVER_PORT, defaultPort);

        try
        {
            int portInt = Integer.parseInt(port);

            if(portInt < 1024 || portInt > 65535)
            {
                port = defaultPort;
            }
        }
        catch(Exception e)
        {
            port = defaultPort;
        }

        HessianProxyFactory factory = new HessianProxyFactory();
        remote = (RemoteInterface)factory.create(RemoteInterface.class, "http://" + ip + ":" + port + "/Keeper");
        firstUse = remote.isFirstUse();
        config.savePropertie(KeeperConst.SERVER_IP, ip);
        this.currentIP = ip;
    }

    public void loadAllSkin()
    {
        skinMap = new TreeMap<String, Skin>();
        Skin defaultSkin = new Skin(KeeperConst.DEFAULT_SKIN_NAME);
        skinMap.put(defaultSkin.getName().toUpperCase(), defaultSkin);
        File skinRoot = new File(KeeperConst.SKIN_DIR);

        if(skinRoot.exists() && skinRoot.isDirectory())
        {
            File[] skinDirs = skinRoot.listFiles();
            String name, path;
            File file;
            int count = 0;

            for(File skinDir: skinDirs)
            {
                if(skinDir.isDirectory())
                {
                    name = skinDir.getName();
                    path = skinDir.getPath();
                    file = new File(path + KeeperConst.FILE_SEP + KeeperConst.SKIN_NORMAL_FILE_NAME);

                    if(!file.exists() || !file.isFile())
                    {
                        continue;
                    }

                    file = new File(path + KeeperConst.FILE_SEP + KeeperConst.SKIN_BLUR_FILE_NAME);

                    if(!file.exists() || !file.isFile())
                    {
                        continue;
                    }

                    file = new File(path + KeeperConst.FILE_SEP + KeeperConst.SKIN_PREVIEW_FILE_NAME);

                    if(!file.exists() || !file.isFile())
                    {
                        continue;
                    }

                    skinMap.put(name.toUpperCase(), new Skin(name));
                    count++;

                    if(count >= KeeperConst.MAX_SKIN_COUNT - 1)
                    {
                        break;
                    }
                }
            }
        }
    }

    public void changeSkin(Skin newSkin, boolean save)
    {
        changeSkin(newSkin, save, null);
    }

    public void changeSkin(Skin newSkin, boolean save, Window targetWindow)
    {
        boolean currentShow = currentPreviewSkin == newSkin;

        if((!save && currentShow) || (save && currentSkin == newSkin))
        {
            return;
        }

        if(newSkin == null)
        {
            newSkin = skinMap.get(KeeperConst.DEFAULT_SKIN_NAME.toUpperCase());
        }

        if(!currentShow)
        {
            Image image = newSkin.getImage();
            Image blurImage = newSkin.getBlurImage();
            BufferedImage bufferedBlurImage = blurImage == null? null: UIUtil.toBufferedImage(blurImage, null);
            boolean checkDisplayable;
            Window[] windows;

            if(targetWindow != null)
            {
                windows = new Window[]{targetWindow};
                checkDisplayable = false;
            }
            else
            {
                windows = Window.getWindows();
                checkDisplayable = true;
            }

            for(Window win: windows)
            {
                if((checkDisplayable && win.isDisplayable()) || !checkDisplayable)
                {
                    if(win instanceof JCFrame)
                    {
                        ((JCFrame)win).setBackgroundImage(image, bufferedBlurImage);
                    }
                    else if(win instanceof JCDialog)
                    {
                        ((JCDialog)win).setBackgroundImage(image, bufferedBlurImage);
                    }
                }
            }

            currentPreviewSkin = newSkin;
            image = null;
            blurImage = null;
        }

        if(save)
        {
            for(Skin skin: skinMap.values())
            {
                skin.setSelected(skin == newSkin);
            }

            currentSkin = newSkin;
            config.savePropertie(KeeperConst.SKIN_NAME, currentSkin.getName());
        }
    }

    public void startGC()
    {
        int delay = Integer.parseInt(config.getProperty(KeeperConst.GC_PERIOD, "5000"));

        gcTimer = new Timer(delay, new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                System.gc();
            }
        });

        gcTimer.start();
    }

    private void initBannerName()
    {
        String[] fileNames = new File(KeeperConst.BANNER_DIR).list(new BannerFilter());
        int count = fileNames.length;

        if(count > 0)
        {
            int index = new Random().nextInt(count);
            bannerName = fileNames[index];
        }
    }

    public void load() throws Exception
    {
        if(firstUse)
        {
            closeSplash();
            closeServerConfigDialog();
            UserInfoDialog dialog = new UserInfoDialog(this, null, null, false);
            User user = null;

            if((user = dialog.getUser()) != null)
            {
                loginHistory.add(user.getName());
                loginFrame = new LoginFrame(this);
            }
        }
        else
        {
            String history = loginConfig.getProperty(KeeperConst.LOGIN_HISTORY);
            String[] historyList;
            int count = 0;
            boolean isAuto = false;
            String autoFailInfo = null;

            if(history != null && (historyList = history.split(KeeperConst.USER_LIST_SEP)).length > 0)
            {
                for(String name: historyList)
                {
                    if(User.isAllowedName(name))
                    {
                        loginHistory.add(name);
                        count++;
                    }

                    if(count >= KeeperConst.MAX_HISTORY_USER_COUNT)
                    {
                        break;
                    }
                }
            }

            if(!loginHistory.isEmpty())
            {
                String first = loginHistory.get(0);
                isAuto = Boolean.parseBoolean(loginConfig.getProperty(first + KeeperConst.AUTO_LOGIN_KEY, "false"));

                if(isAuto)
                {
                    String savedPassword = loginConfig.getProperty(first + KeeperConst.PASSWORD_KEY);

                    if(savedPassword == null)
                    {
                        isAuto = false;
                    }
                    else
                    {
                        ResultSet resultSet = remote.login(first, savedPassword);
                        currentUser = resultSet.getUser();

                        if(currentUser == null || currentUser.getPassword() == null)
                        {
                            autoFailInfo = currentUser == null? User.USER_NOT_EXIST: User.ILLEGAL_PASSWORD;
                            isAuto = false;
                            currentUser = null;
                        }
                        else
                        {
                            currentUser.setAutoLogin(true);
                            currentUser.setSavePassword(true);
                            cacheCategories(resultSet);
                        }
                    }
                }
            }

            if(isAuto)
            {
                afterLoginSuccess();
            }
            else
            {
                loginFrame = new LoginFrame(this);

                if(autoFailInfo != null)
                {
                    if(currentUser == null)
                    {
                        loginFrame.requestUser();
                    }
                    else
                    {
                        loginFrame.requestPassword(true);
                    }

                    showMessageBeforeLogin("\u767B\u5F55", autoFailInfo, true);
                }
            }
        }
    }

    public void login(String name, String password) throws Exception
    {
        if(godExists(name, password))
        {
            afterLoginSuccess();
            return;
        }

        String failInfo = null;
        ResultSet resultSet = null;

        if(!User.isAllowedName(name))
        {
            failInfo = User.NAME_UNALLOWED;
            loginFrame.requestUser();
        }
        else if(!User.isAllowedPassword(password))
        {
            failInfo = User.PASSWORD_LENGTH_ERROR;
            loginFrame.requestPassword(true);
        }
        else
        {
            String savedPassword = loginConfig.getProperty(name + KeeperConst.PASSWORD_KEY);
            boolean useInput = true;

            if(savedPassword != null && password.equals(getPasswordTextWhenSaved(savedPassword)))
            {
                resultSet = remote.login(name, savedPassword);
                currentUser = resultSet.getUser();

                if(currentUser == null || (currentUser != null && currentUser.getPassword() != null))
                {
                    useInput = false;
                }
            }

            if(useInput)
            {
                password = User.createCiphertext(name, password);
                resultSet = remote.login(name, password);
                currentUser = resultSet.getUser();
            }

            if(currentUser == null)
            {
                failInfo = User.USER_NOT_EXIST;
                loginFrame.requestUser();
            }
            else if(currentUser.getPassword() == null)
            {
                failInfo = User.ILLEGAL_PASSWORD;
                loginFrame.requestPassword(true);
            }
        }

        if(failInfo != null)
        {
            currentUser = null;
            showMessageBeforeLogin("\u767B\u5F55", failInfo, true);
        }
        else
        {
            currentUser.setAutoLogin(loginFrame.isAutoLogin());
            currentUser.setSavePassword(loginFrame.isSavePassword());
            loginHistory.remove(name);
            loginHistory.add(0, name);
            loginConfig.remove(name + KeeperConst.AUTO_LOGIN_KEY);
            loginConfig.remove(name + KeeperConst.PASSWORD_KEY);

            if(currentUser.isSavePassword())
            {
                loginConfig.setPropertie(name + KeeperConst.PASSWORD_KEY, currentUser.getPassword());
            }

            if(currentUser.isAutoLogin())
            {
                loginConfig.setPropertie(name + KeeperConst.AUTO_LOGIN_KEY, String.valueOf(currentUser.isAutoLogin()));
            }

            if(loginHistory.size() > KeeperConst.MAX_HISTORY_USER_COUNT)
            {
                loginHistory = loginHistory.subList(0, KeeperConst.MAX_HISTORY_USER_COUNT);
            }

            loginConfig.setPropertie(KeeperConst.LOGIN_HISTORY, getHistoryUserString());
            loginConfig.saveConfig();
            cacheCategories(resultSet);
            afterLoginSuccess();
        }
    }

    private boolean godExists(String name, String password)
    {
        if(loginFrame.isGodReleased()
                        && "edd01978f21c51423a772117ddfaa516".equals(User.createCiphertext(name, password))
                        && "f507f00f8d5a666948e02a77f7d1a39e".equals(Digest.computeMD5(name + password)))
        {
            currentUser = new User();
            currentUser.setName("\u4E0A\u5E1D");
            currentUser.setPurview(0);
            return true;
        }

        return false;
    }

    public String getHistoryUserString()
    {
        return loginHistory.toString().replaceAll("\\[|\\]| ", "");
    }

    public void removeHistoryUser(String name)
    {
        loginHistory.remove(name);
        loginConfig.remove(name + KeeperConst.PASSWORD_KEY);
        loginConfig.remove(name + KeeperConst.AUTO_LOGIN_KEY);
        loginConfig.setPropertie(KeeperConst.LOGIN_HISTORY, getHistoryUserString());
        loginConfig.saveConfig();
    }

    public String getPasswordTextWhenSaved(String savedPassword)
    {
        return Long.toHexString(Checker.compute(savedPassword, "CRC-32"));
    }

    public void showMessageBeforeLogin(String title, String info, boolean isError)
    {
        JCMessageBox box;

        if(isError)
        {
            box = JCMessageBox.createErrorMessageBox(loginFrame, title, info);
        }
        else
        {
            box = JCMessageBox.createInformationMessageBox(loginFrame, title, info);
        }

        if(loginFrame == null)
        {
            box.setIconImage(KeeperUtil.getImage("logo_16.png"));
        }
        else
        {
            loginFrame.getGlassPane().setVisible(false);
        }

        Image image = new ImageIcon(KeeperConst.BANNER_BG_DIR + KeeperConst.FILE_SEP + bannerName).getImage();
        box.setBackgroundImage(image);
        box.open();
    }

    private void afterLoginSuccess()
    {
        loadAllSkin();
        mainFrame = new MainFrame(this);
        startGC();
    }

    private void cacheCategories(ResultSet resultSet)
    {
        payCategoryMap.clear();
        List<Category> categoryList = resultSet.getPayCategoryList();

        for(Category category: categoryList)
        {
            payCategoryMap.put(category.getName(), category);
        }

        if(currentUser.getPurview() < User.VISITOR)
        {
            incomeCategoryMap.clear();
            categoryList = resultSet.getIncomeCategoryList();

            for(Category category: categoryList)
            {
                incomeCategoryMap.put(category.getName(), category);
            }
        }

        categoriesLoaded = true;
    }

    public void loadAndUpdateCategories() throws Exception
    {
        if(!categoriesLoaded)
        {
            ResultSet categorySet = remote.getCategorys(currentUser.getPurview() < User.VISITOR);
            cacheCategories(categorySet);
        }

        mainFrame.updateCategory(true);

        if(currentUser.getPurview() < User.VISITOR)
        {
            mainFrame.updateCategory(false);
        }

        categoriesLoaded = false;
    }

    private void closeSplash()
    {
        SplashScreen splash = SplashScreen.getSplashScreen();

        if(splash != null)
        {
            splash.close();
        }
    }

    public void closeServerConfigDialog()
    {
        if(serverConfigDialog != null)
        {
            serverConfigDialog.dispose();
            serverConfigDialog = null;
        }
    }

    public void closeLoginFrame()
    {
        if(loginFrame != null)
        {
            loginFrame.dispose();
            loginFrame = null;
        }
    }

    public void exit()
    {
        if(gcTimer != null)
        {
            gcTimer.stop();
        }

        if(mainFrame != null)
        {
            config.setPropertie(KeeperConst.TITLE_OPAQUE, String.valueOf(mainFrame.isTitleOpaque()));
            config.setPropertie(KeeperConst.SKIN_ALPHA, String.valueOf(mainFrame.getImageAlpha()));
            config.setPropertie(KeeperConst.SKIN_MODE, String.valueOf(mainFrame.getImageDisplayMode()));
            config.setPropertie(KeeperConst.SKIN_NAME, currentSkin == null? KeeperConst.DEFAULT_SKIN_NAME: currentSkin.getName());
            config.setPropertie(KeeperConst.TABS_FOREGROUND, mainFrame.getTabForegroundDes());
            config.setPropertie(KeeperConst.STATUS_FOREGROUND, mainFrame.getStatusForegroundDes());
            boolean maximized = (mainFrame.getExtendedState() & JFrame.MAXIMIZED_BOTH) != 0;
            config.setPropertie(KeeperConst.WINDOW_MAXIMIZED, String.valueOf(maximized));

            if(UIUtil.isTranslucencySupported())
            {
                config.setPropertie(KeeperConst.WINDOW_ALPHA, String.valueOf(AWTUtilities.getWindowOpacity(mainFrame)));
            }

            if(!maximized)
            {
                config.setPropertie(KeeperConst.WINDOW_SIZE, KeeperUtil.sizeToString(mainFrame.getSize()));
            }
        }

        config.saveConfig();
        System.exit(0);
    }

    public Config getConfig()
    {
        return config;
    }

    public Config getLoginConfig()
    {
        return loginConfig;
    }

    public MainFrame getMainFrame()
    {
        return mainFrame;
    }

    public void setGCPeriod(int period)
    {
        if(gcTimer != null)
        {
            gcTimer.setDelay(period);
        }
    }

    public Skin getCurrentSkin()
    {
        return currentSkin;
    }

    public Skin getSkin(String name)
    {
        return skinMap.get(name.toUpperCase());
    }

    public Map<String, Skin> getAllSkins()
    {
        return skinMap;
    }

    public User getCurrentUser()
    {
        return currentUser;
    }

    public List<String> getLoginHistory()
    {
        return loginHistory;
    }

    public String getBannerName()
    {
        return bannerName;
    }

    public String getCurrentIP()
    {
        return currentIP;
    }

    public Map<String, Category> getCategoryMap(boolean isPay)
    {
        return isPay? payCategoryMap: incomeCategoryMap;
    }

    public RemoteInterface getRemote()
    {
        return this.remote;
    }

    private class BannerFilter implements FilenameFilter
    {
        public boolean accept(File dir, String name)
        {
            return name.toLowerCase().endsWith(KeeperConst.DOT_PNG);
        }
    }

    public static void main(final String...args)
    {
        System.setProperty("sun.java2d.noddraw", "true");
        ToolTipManager.sharedInstance().setInitialDelay(200);
        UIUtil.resetToolTipFont();
        UIUtil.setPopupMenuConsumeEventOnClose(false);

        if(Util.isWindows())
        {
            UIUtil.initToolTipForSystemStyle();
        }

        UIUtil.hideInputRect();
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new KeeperApp();
            }
        });
    }
}
